/**
 This server side script library implements the tagCloud class
 which is used by the "authorCloud" and "categoryCloud" custom controls.
 
*/
CLOUD_SIZE			= ".size";
VIEW_AUTHOR_CLOUD	= "xpAuthorCloud";

TAG_COMMON_NAME		= 0;
TAG_LOOKUP_NAME		= 1;
TAG_COUNT			= 2;
TAG_WEIGHT			= 3;

DEBUG_OUTPUT		= false;

// the class prototype
tagCloud = function() {

	// private class members
	_db:	NotesDatabase = null;
	_view:	NotesView = null;		
						
	//-----------------
	this.prototype.init = function() {
	//-----------------
	
		try {

			_db = database;
					
			if (this.isCloudCacheInvalid())
				this.createCloudFromView();
				
		} catch (e) {
			print (e);
		}
	}

	//--------------------------------
	this.prototype.isCloudCacheInvalid = function() {
	//--------------------------------
	
		var PREFIX_LAST_COUNT	= ".lastCount";
		var PREFIX_LAST_TIME	= ".lastTime"
		var CACHE_TIMEOUT_SECS	= Number(context.getProperty("app.cloud.cache.timeout", "120"));

		debug ("Cache timeout is " + CACHE_TIMEOUT_SECS + " seconds");
							
		try {

			var CACHED_COUNT	= compositeData.cloudView + PREFIX_LAST_COUNT;
			var CACHED_TIME		= compositeData.cloudView + PREFIX_LAST_TIME;
			
			debug ("Checking cache for view " + compositeData.cloudView);
						
			// if a cloud hasn't been built for the view at all yet, do it now
			if (applicationScope.get(compositeData.cloudView) == null) {
				
				_view = _db.getView(compositeData.cloudView);
				applicationScope.put(CACHED_COUNT,	_view.getEntryCount());
				applicationScope.put(CACHED_TIME,	new Date().getTime().toString());
				debug ("Building new tag cloud from scratch for view " + compositeData.cloudView);
				return true;
			}

			// ----------------
			// 1st level cache: Let the minimum CACHE_TIMEOUT_SECS elapse before even considering refreshing the cache
			// ----------------

			var iCurrentTime	= new Date().getTime();
			var iCachedTime		= Number(new String(applicationScope.get(CACHED_TIME)));
			var iDiffSecs		= Math.ceil((iCurrentTime-iCachedTime)/1000);

			if (iDiffSecs < CACHE_TIMEOUT_SECS) {
				debug ("1st level cache still active for " + (CACHE_TIMEOUT_SECS - iDiffSecs) + " more seconds");
				return false;
			} else {
				// more than CACHE_TIMEOUT_SECS have elapsed, cache the new timestamp and check the 2nd level cache
				debug ("1st level cache has expired, checking 2nd level cache");
				applicationScope.put(CACHED_TIME, iCurrentTime.toString());
			}

			// ----------------
			// 2nd level cache: Compare if the view entry counts have changed (not perfect, but 'good enough')
			// ----------------
			
			// get the current view entry count and the last one from the cache
			_view				= _db.getView(compositeData.cloudView);
			var iViewLastCount	= _view.getEntryCount();
			var iCacheLastCount	= Number(applicationScope.get(CACHED_COUNT));
			
			if (iViewLastCount != iCacheLastCount) {
				// view counts have changed, update the cache
				debug ("View entry counts differ, invalidating 2nd level cache");
				applicationScope.put(CACHED_COUNT, iViewLastCount);
				return true;
			} else {
				// view count hasn't been changed since, cache is still valid
				debug ("View entry counts are still the same, 2nd level cache is still valid");
				return false;
			}
			
		} catch (e) {
			print (e);
		}
	}
	
	//--------------------------------
	this.prototype.createCloudFromView = function() {
	//--------------------------------		
		try {
			
			var nav:	NotesViewNavigator = null;
			var entry:	NotesViewEntry = null;
			var cloud:	Array = [];
			var name:	string = null;
			var nameCN:	string = null;
			var index:	int = 0;
			var count:	int = 0;
			var low:	int = 1000;	// seed initial, artificially high number
			var high:	int = 0;
			var max:	int = 10;	// the maximum relative weight any tag can have
										
			// lock the view
			//_view				= _db.getView(compositeData.cloudView);
			_view.AutoUpdate = false;
						
			// get a navigator to iterate over the view		
			nav		= _view.createViewNav();
			entry	= nav.getFirst();

			if (entry == null) {
				// no entries found, get out
				applicationScope.put(compositeData.cloudView + CLOUD_SIZE, "0");
				return;
			}

			// iterate over the view
			while (entry != null) {
				if (entry.isValid()) {
					if (entry.isCategory()) {
						
						name	= entry.getColumnValues()[1];
						count	= entry.getChildCount();
						
						// compute the common name for the author cloud only
						if (compositeData.cloudView == VIEW_AUTHOR_CLOUD ) {
							nameCN = session.createName(name).getCommon();							
						} else {
							nameCN = name;
						}
											
						// find lowest and highest range counts
						if (count > high)	{high = count;}
						if (count < low)	{low = count;}
	
						// add to array (leave weight as null for now, we'll compute that later)
						cloud[index++] = [nameCN, name, count.toString(), null];
					}				
				}
				entry = nav.getNextCategory();
			}
	
			// now compute the relative weight of each tag
			var range:int	= high-low;
			var factor:int	= max*low;
			var ceil:int	= max-1;
			
			
			for (i=0; i < cloud.length; i++) {
				//Frank van der Linden: I added this if then else, to determine the weight will have a value
				if(range == 0){
					cloud[i][TAG_WEIGHT] = 0;
				}else{
					cloud[i][TAG_WEIGHT] = Math.round(((ceil / range) * cloud[i][TAG_COUNT]) + (high - factor) / range);
				}	
			}
	
			// debug output of the final array	
			if (DEBUG_OUTPUT == true) {
				for (i=0; i < cloud.length; i++) {print(cloud[i])}
			}
				
			// store cloud and size for this view in the global applicationScope
			applicationScope.put(compositeData.cloudView, cloud);
			applicationScope.put(compositeData.cloudView + CLOUD_SIZE, cloud.length.toString());
			
		} catch (e) {
			print (e);
		}
		
	}
}

function debug(sMessage:string) {
	try {
		if (DEBUG_OUTPUT == true) print (sMessage);		
	} catch (e) {
		print (e);
	}
}
